"""
小市值策略
"""
import pandas as pd
from datetime import datetime, timedelta
from core.strategy import BaseStrategy
from config.config import ConfigManager
from core.models import Order, OrderDirection, OrderType
from utils.logger import sys_logger

logger = sys_logger.getChild('Strategy')

class FactorCaptitalStrategy(BaseStrategy):
    """因子策略示例"""

    def __init__(self, config: ConfigManager):
        """
        初始化小市值策略
        :param symbols: 监控的标的代码列表
        """
        super().__init__()

        self._config = config
        self.index = self._config.get('index')
        self.frequency = self._config.get('strategy.frequency')

        self.current_date = None

        self._validate_symbols()

        self.parameters = (f"FactorCaptitalStrategy: index={self.index}, frequency={self.frequency}")
        logger.info(f"因子策略初始化完成 | {self.parameters}")

    def _validate_symbols(self):
        """验证配置中的股票代码合法性"""
        symbols = self._config.get('symbols', [])
        invalid_symbols = []

        for symbol in symbols:
            if not self._is_valid_symbol(symbol):
                invalid_symbols.append(symbol)

        if invalid_symbols:
            error_msg = f"检测到非法股票代码: {invalid_symbols}"
            logger.error(error_msg)
            raise ValueError(error_msg)

        self.symbols = symbols

    def is_execution_day(self) -> bool:
        """
        判断是否为当月第一个交易日
        返回True需要满足：当天是交易日，且当月历史自然日内没有更早的交易日
        :return: bool
        """
        try:
            if self.frequency == 'daily':
                # 每日执行
                return True
            elif self.frequency == 'weekly':
                # 每周执行
                # 获取当前北京时间
                current_date = self._current_date

                # 获取当周第一天
                first_day = current_date - timedelta(days=current_date.weekday())

                # 遍历从当周首日到昨日范围
                for delta in range(0, (current_date - first_day).days):
                    check_date = first_day + timedelta(days=delta)

                    # 发现历史交易日立即返回False
                    if self.engine.is_trading_day(check_date):
                        return False

                # 当前为当周首个交易日
                return True
            elif self.frequency == 'monthly':
                # 每月执行
                # 获取当前北京时间
                current_date = self._current_date

                # 获取当月第一天
                first_day = current_date.replace(day=1)

                # 遍历从当月首日到昨日范围
                for delta in range(0, (current_date - first_day).days):
                    check_date = first_day + timedelta(days=delta)

                    # 发现历史交易日立即返回False
                    if self.engine.is_trading_day(check_date):
                        return False

                # 当前为当月首个交易日
                return True
            else:
                # 默认不执行
                return False

        except Exception as e:
            logger.error(f"Error checking first execution day: {str(e)}", exc_info=True)
            return False

    def _calculate_position_size(self, price: float, direction: OrderDirection, available_cash: float) -> int:
        """
        基于风险管理的头寸计算
        :param price: 标的当前价格
        :param direction: 交易方向
        :return: 建议交易数量（整百股数）
        """
        logger.debug("开始头寸计算 | 方向: %s 价格: %.3f", direction.value, price)

        # 参数校验
        if not self.portfolio:
            logger.error("持仓管理器未初始化")
            raise RuntimeError("持仓管理器未初始化")

        if not self.risk:
            logger.error("风险管理器未初始化")
            raise RuntimeError("风险管理器未初始化")

        # 获取风险参数
        risk_ratio = self.risk.params.per_trade_risk
        max_position_ratio = self.risk.params.max_position_ratio
        min_position_ratio = self.risk.params.min_position_ratio
        max_portfolio_exposure = self.risk.params.max_portfolio_exposure
        total_equity = self.portfolio.total_equity

        # 计算理论头寸
        if direction == OrderDirection.BUY:
            # 买入方向计算逻辑
            risk_based_value = available_cash * risk_ratio
            max_position_value = total_equity * max_portfolio_exposure * max_position_ratio
            min_position_value = total_equity * max_portfolio_exposure * min_position_ratio

            available_value = min(risk_based_value, max_position_value)

            if available_value < min_position_value:
                available_value = 0

            available_shares = available_value // (price * 100)

            position = int(available_shares) * 100  # 转换为股数
            logger.debug(f"买入头寸计算 | 可用资金: {available_cash:,.3f} 风险比例: {risk_ratio*100:.3f}% 理论数量: {position:d}")
        else:
            # 卖出方向计算逻辑
            position = 0

        # 保证最小交易单位
        final_position = max(position, 100) if position > 0 else 0

        logger.debug("最终头寸: %d 股", final_position)

        return final_position

    def _calc_target_symbols(self):
        # 计算目标股票列表
        self.target_symbols = []

        data_records = []

        for symbol in self.index_symbols:
            try:
                # 验证历史数据完整性
                latest_data = self._latest_data.get(symbol)
                if latest_data is None or (len(latest_data) == 0):
                    logger.warning(f"{symbol} 缺失最新股票数据，跳过处理")
                    continue

                # 验证财务数据完整性
                fin_data = self._financial_data.get(symbol)
                if fin_data is None or fin_data.empty:
                    logger.warning(f"{symbol} 缺失财务数据，跳过处理")
                    continue

                # 提取最新收盘价（最后一条记录）
                latest_price= latest_data['lastPrice']

                # 提取最新总股本（最后一条记录）
                latest_capital = fin_data['total_capital'].iloc[-1]

                # 数据有效性检查
                if latest_price <= 0 or latest_capital <= 0:
                    logger.warning(f"Symbol {symbol}存在无效数据（收盘价:{latest_price}/总股本:{latest_capital}）")
                    continue

                # 记录有效数据
                data_records.append({
                    'symbol': symbol,
                    'close': latest_price,
                    'total_capital': latest_capital
                })

            except KeyError as e:
                logger.error(f"Symbol {symbol}字段缺失: {str(e)}")
            except IndexError as e:
                logger.error(f"Symbol {symbol}数据索引错误: {str(e)}")
            except Exception as e:
                logger.error(f"处理{symbol}时发生意外错误: {str(e)}", exc_info=True)

        # 无有效数据时提前返回
        if not data_records:
            logger.error("无有效数据可供计算，目标列表置空")
            self.target_symbols = []
            return

        # 创建数据集
        df = pd.DataFrame(data_records)

        # 计算总市值（假设总股本单位为股）
        df['market_cap'] = df['close'] * df['total_capital']

        # 按市值升序排序
        df_sorted = df.sort_values('market_cap', ascending=True)

        # 选取前10小市值股票
        target_count = 10
        if len(df_sorted) >= target_count:
            target_df = df_sorted.head(target_count)
        else:
            target_df = df_sorted
            logger.warning(f"有效标的不足{target_count}只，实际选取{len(target_df)}只")

        # 生成最终目标列表
        self.target_symbols = target_df['symbol'].tolist()

        # 记录详细信息
        logger.info(
            "目标标的计算完成 | 数量: %d | 总市值范围: [%.2f亿 ~ %.2f亿] | 股票列表: %s",
            len(self.target_symbols),
            target_df['market_cap'].min() / 1e8,
            target_df['market_cap'].max() / 1e8,
            self.target_symbols
        )

        #
        logger.info(f"目标标的列表\n{target_df}")

    def _generate_sell_orders(self):
        """实盘卖单生成"""
        try:
            logger.info("开始处理 %s 实盘卖单数据", self.index)

            for symbol in self.portfolio.symbols:
                # 持仓股票不在目标股票中, 则全部卖出
                if symbol not in self.target_symbols:

                    direction = OrderDirection.SELL

                    # 获取当前持仓信息
                    position = self.portfolio.get_position(symbol)
                    position_size = position.available_volume

                    latest_data = self._latest_data[symbol]
                    price = round(latest_data['lastPrice'], 2)     #使用最新股价数据

                    logger.debug("%s 实际可卖出数量: %d", symbol, position_size)

                    # 过滤无效头寸
                    if position_size <= 0:
                        logger.warning("无效交易数量 | 方向: %s 建议数量: %d", direction.value, position_size)
                        continue

                    logger.info("生成交易订单 | %s %s %d股 @ %.3f", symbol, direction.value, position_size, price)

                    order = Order(symbol = symbol,
                                  direction = direction,
                                  price = price,
                                  volume = position_size,
                                  type = OrderType.MARKET)

                    # 添加订单到引擎中
                    self.engine.add_order(order)

        except Exception as e:
            logger.error("实盘卖单生成异常: %s", str(e), exc_info=True)

    def _generate_buy_orders(self):
        """实盘订单生成"""
        try:
            logger.info("开始处理 %s 实盘买单数据", self.index)

            to_buy_symbols = []

            # 计算需要购买的股票
            for symbol in self.target_symbols:
                if symbol not in self.portfolio.symbols:
                    to_buy_symbols.append(symbol)

            # 本地持仓无改变, 无需购买股票
            if len(to_buy_symbols) == 0:
                logger.info("本地持仓无改变, 无需购买股票")
                return

            # 计算当前可用资金
            max_portfolio_exposure = self.risk.params.max_portfolio_exposure
            total_equity = self.portfolio.total_equity
            total_market_value = self.portfolio.total_market_value

            available_cash_exposure = total_equity * max_portfolio_exposure - total_market_value

            available_cash = min(self.portfolio.available_cash, available_cash_exposure)
            available_cash = max(available_cash, 0)

            available_cash_per_symbol = available_cash // len(to_buy_symbols)

            # 每只股票平均购买
            for symbol in to_buy_symbols:
                latest_data = self._latest_data[symbol]
                price=round(latest_data['lastPrice'], 2)     #使用收盘价数据估计买入量

                direction = OrderDirection.BUY

                # 买入时使用基于资金的头寸计算
                position_size = self._calculate_position_size(price, direction, available_cash_per_symbol)

                logger.debug("%s 实际可买入数量 | 理论: %d", symbol, position_size)

                # 过滤无效头寸
                if position_size <= 0:
                    logger.warning("无效交易数量 | %s 方向: %s 价格: %.2f 建议数量: %d", symbol, direction.value, price, position_size)
                    continue

                logger.info("生成交易订单 | %s %s %d股 @ %.3f", symbol, direction.value, position_size, price)

                order = Order(symbol = symbol,
                              direction = direction,
                              price = price,
                              volume = position_size,
                              type = OrderType.MARKET)

                # 添加订单到引擎中
                self.engine.add_order(order)

        except Exception as e:
            logger.error("实盘买单生成异常: %s", str(e), exc_info=True)

    def pre_market(self) -> None:
        """执行开盘前准备"""
        # 获取最新股票列表
        self.index_symbols = self.market.get_index_symbols(index=self.index)
        # 合并股票列表
        self.symbols = list(set(self.index_symbols + self.portfolio.symbols))

        # 加载最新财务数据
        end_date   = datetime.now().date()
        start_date = end_date - timedelta(days=2*365)

        logger.info("加载历史财务数据范围: %s 至 %s", start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d"))

        for idx, symbol in enumerate(self.symbols):
            df = self.market.get_local_financial_data(symbol, start_date, end_date)

            if not df.empty:
                #数据清洗
                data = df[df['total_capital'] != 0]

                self.load_financial_data(symbol, data)
                logger.debug("已加载 %s 历史财务数据，数据量: %d 条, 进度: %d/%d", symbol, len(data), idx, len(self.symbols))


    def on_close(self) -> None:
        """执行收盘交易"""
        # 获取最新股票数据
        data = self.engine.get_latest_data(self.symbols)

        for idx, symbol in enumerate(self.symbols):
            if data.get(symbol):
                self.load_latest_data(symbol, data[symbol])
                logger.debug("已加载 %s 最新股票数据，进度: %d/%d", symbol, idx, len(self.symbols))

        # 计算目标股票列表
        self._calc_target_symbols()

        # 生成当日卖单
        self._generate_sell_orders()

        # 提交当日卖单
        self.engine.place_orders()

        # 等待订单全部执行完成
        self.engine.wait_orders_completion()

        # 生成当日买单
        self._generate_buy_orders()

        # 提交当日买单
        self.engine.place_orders()

        # 等待订单全部执行完成
        self.engine.wait_orders_completion()